package empe;
/**
 * 
 */


import java.util.Random;

 
/**
 * Classe que contem e gere a lista de individuos
 */
public class Lista_individuo {
	
	private Individuo lista;
	
	private Individuo best;
	
	private long populacao_actual;
	
	private Cap cap;
	
	private long num_epidemias;
	
	/**
	 * Construtor que recebe a referencia para a cap
	 * @param cap referencia para a cap onde vao estar os eventos dos individuos
	 */
	protected Lista_individuo(Cap cap)
	{
		lista=null;
		best=null;
		populacao_actual=0;
		num_epidemias = 0;
		this.cap=cap;
		return;
	}
	
	/**
	 * Cria e retorna 1 individuo com uma matriz previamente calculada.
	 * Posteriormente cria os eventos relativos ao individuo criado
	 * e insere-os na cap.
	 * @param matriz matriz previamente calculada para o novo individuo
	 * @param tempo_criacao tempo em que esta a ser criado o individuo
	 */
	protected void criar_individuo (int[][] matriz, int tempo_criacao)
	{
		Individuo individuo= new Individuo(matriz);
		Cap_elemento evento;
		
		inserir_individuo(individuo);

		evento= new Morte(tempo_criacao, individuo);
		
		cap.insere_elemento(evento);
		
		evento=new Mutacao(tempo_criacao, individuo);
		
		cap.insere_elemento(evento);
		
		evento= new Reproducao(tempo_criacao, individuo);
		
		cap.insere_elemento(evento);

		return;
	}
	
	/**
	 * Faz uma insersão ordenada do individuo
	 * -Verifica logo se pode ser Best
	 * -Incrementa a populacao actual
	 * @param individuo individuo ja criado a ser inserido
	 */
	protected void inserir_individuo(Individuo individuo)
	{
		
		Individuo tmp;
		
		if (lista==null)
		{
			lista=individuo;
			individuo.anterior=null;
			individuo.proximo=null;
		}		
		else
		{
			tmp=lista;
			Individuo anterior=tmp;
			
			while (individuo.ler_conforto()<tmp.ler_conforto())
			{
				anterior=tmp;
				tmp=tmp.proximo;
				if(tmp==null)
				{
					break;
				}
			}
			if(tmp==null) /*é o ultimo da lista*/
			{
				individuo.proximo=null;
				individuo.anterior=anterior;
				anterior.proximo=individuo;
			}
			else if(tmp==lista) /*ficará em primeiro*/
			{
				lista=individuo;
				tmp.anterior=individuo;
				individuo.proximo=tmp;
				individuo.anterior=null;
			}
			else
			{
				individuo.proximo=tmp;
				individuo.anterior=tmp.anterior;
				individuo.anterior.proximo=individuo;
				tmp.anterior=individuo;
			}
		}
		
		if (best==null)
		{
			best=individuo.clonar();
		}
		else if (individuo.ler_conforto()>best.ler_conforto())
		{
			best=individuo.clonar();
		}
		
		populacao_actual++;
	
	}
	
	/**
	 * Remove individuo da lista de individuos 
	 * -pede a cap para retirar todos os eventos correspondentes ao individuo em 
	 * questão (util para epidemia).
	 * -decrementa a populacao actual
	 * @param individuo individuo a ser removido
	 */
	protected void remover_individuo (Individuo individuo)
	{	
		
		if(individuo.anterior==null) /* e' o primeiro*/
		{
			if(individuo.proximo!=null) /*não está sozinho*/
			{
				individuo.proximo.anterior=null;
			}
			lista=individuo.proximo;
			
			//individuo.proximo=null;
		}
		else
		{
			if(individuo.proximo!=null) /*nao e' o ultimo*/
			{
				individuo.proximo.anterior=individuo.anterior;
			}
			
			individuo.anterior.proximo=individuo.proximo;
			//individuo.anterior=individuo.proximo=null;
		}
		
		
		
		Evento tmp= (Evento) individuo.eventos;
		Evento tmp2;
		
		while(tmp!=null)
		{
			tmp2=tmp;
			tmp=tmp.prox_evento_individuo;
			cap.retira_elemento(tmp2);
		}
		populacao_actual--;
		
	}
	
	/**
	 * Retorna o nº de habitantes na lista
	 * @return nº de habitantes actualmente na lista
	 */
	protected long ler_populacao_actual()
	{
		return populacao_actual;
	}
	
	/**
	 * Retorna o nº de epidemias ja realizadas
	 * @return nº de epidemias ja contabilizadas
	 */
	protected long ler_num_epidemias() {
		
		return num_epidemias;
	}
	
	/**
	 * Retorna a referencia do melhor individuo
	 * @return retorna a referencia para o individuo com maior conforto registado
	 */
	protected Individuo ler_best() {
		
		return best;
	}
	
	/**
	 * Corre epidemia:
	 * -os primeiros 5 permanecem;
	 * -os restantes tem uma probabilidade de 2/3*conforto de sobreviver
	 */
	protected void epidemia()
	{
		
		Individuo tmp, tmp2;
		Random random = new Random();
		
		tmp=this.lista;
		
		for (int i=0; i<5; i++) /*o lista já e' um individuo valido*/
		{
			tmp=tmp.proximo;
		}
		
		while(tmp!=null)
		{
			tmp2=tmp;
			tmp=tmp.proximo;
			if(random.nextDouble()>((2*tmp2.ler_conforto())/3))
			{
				remover_individuo(tmp2);
			}
		}
	
		num_epidemias++;
	}
	
	/**
	 * Muta um individuo, ordena e caso tenha melhorado o 
	 * conforto, verifica se e o novo Best
	 * @param individuo individuo a mutar
	 */
	protected void mutar_individuo(Individuo individuo)
	{
		
		boolean melhorou;
		
		melhorou=individuo.mutar();
		ordenar_individuo(individuo, melhorou);
		
		if(melhorou==true)
		{
			if(individuo.ler_conforto()>best.ler_conforto())
			{
				best=individuo.clonar();
				//best.anterior = best.proximo = null; -> OUTRA ABOMINAÇÃO!!!
			}
		}

	}
	
	/**
	 * Ordena um individuo que se encontra na lista de individuos apos ter-se mutado 
	 * @param individuo individuo a ser ordenado
	 * @param melhorou indica se o individuo melhorou ou nao durante a mutacao
	 */
	private void ordenar_individuo(Individuo individuo, boolean melhorou)
	{
		
		Individuo tmp;
		
		//Caso de a lista ter apenas 1 elemento
		if(individuo.proximo==null & individuo.anterior==null) return;
		
		//Caso o individuo tenha melhorado
		if (melhorou==true)
		{
			//caso ja fosse o 1º
			if (individuo.anterior==null) return; 
			//Caso já esteja ordenado
			if (individuo.anterior.ler_conforto()>=individuo.ler_conforto()) return;
			
			//Dissociar o individuo actual da lista para ser colocado na posição correcta
			tmp = individuo.anterior;
			tmp.proximo=individuo.proximo;
			if(tmp.proximo!=null) tmp.proximo.anterior=tmp; //condição pro caso de ser o ultimo
			individuo.proximo=individuo.anterior=null;
			
			tmp=tmp.anterior;
			//procurar posição correcta 
			while(tmp!=null)
			{
				//Caso tenha chegado à posição certa :) 
				if(tmp.ler_conforto()>=individuo.ler_conforto()) break;
				
				tmp=tmp.anterior;
			}
			//caso tenha chegado até nulo
			if(tmp==null)
			{
				individuo.proximo=lista;
				lista.anterior=individuo;
				lista=individuo;
			}
			else //Caso contrário
			{
				individuo.proximo=tmp.proximo;
				individuo.anterior=tmp;
				if (individuo.proximo!=null)
					individuo.proximo.anterior=individuo;
				tmp.proximo=individuo;
			}
		}//fim da hipotese melhoria
		
		//Caso o individuo tenha Piorado
		if (melhorou==false)
		{
			//caso ja fosse o último
			if (individuo.proximo==null) return; 
			//Caso já esteja ordenado
			if (individuo.proximo.ler_conforto()<=individuo.ler_conforto()) return;
			
			//Dissociar o individuo actual da lista para ser colocado na posição correcta
			tmp = individuo.proximo;
			tmp.anterior=individuo.anterior;
			if(tmp.anterior!=null) tmp.anterior.proximo=tmp; //condição pro caso de ser o 1º
			else lista=tmp;
			individuo.proximo=individuo.anterior=null;
			
			Individuo tmp_ant=tmp;
			tmp=tmp.proximo;
			//procurar posição correcta 
			while(tmp!=null)
			{
				//Caso tenha chegado à posição certa :) 
				if(tmp.ler_conforto()<=individuo.ler_conforto()) break;
				
				tmp_ant=tmp;
				tmp=tmp.proximo;
			}
			//caso tenha chegado até ao fim da lista
			if(tmp==null)
			{
				individuo.proximo=null;
				individuo.anterior=tmp_ant;
				tmp_ant.proximo=individuo;
			}
			else //Caso contrário
			{
				individuo.anterior=tmp.anterior;
				individuo.proximo=tmp;
				if(individuo.anterior != null)  /*ATENCAO: individuo pode ser o primeiro?*/
					individuo.anterior.proximo=individuo;
				tmp.anterior=individuo;
			}
		}//fim da hipotese de pior	
		
	}
	
	/**
	 * Gera uma nova matriz para um novo individuo
	 * da populacao inicial...
	 * @return uma matriz gerada aleatoriamente da matriz referencia
	 */
	private int[][] gerar_matriz()
	{
		int[][] tabela= new int[Controlo.ler_n()][Controlo.ler_m()];
		int[][] referencia = Controlo.ler_C();
		
		for(int i=0; i<Controlo.ler_n();i++)
		{
			for(int j=0; j<Controlo.ler_m();j++)
			{
				tabela[i][j]=0;
			}
		}
		
		Random random = new Random();
		
		for(int m=0; m<Controlo.ler_m(); m++)
		{			
			int proc=random.nextInt(Controlo.ler_n());
			
			tabela[proc][m]=referencia[proc][m];
		}
		
		return tabela;
	}
	
	/**
	 * Gera populacao inicial:
	 * -cria uma nova matriz para cada individuo
	 * -Cria os 3 tipos de eventos e pede a cap para os inserir
	 */
	protected void init_populacao ()
	{
		while(populacao_actual<Controlo.ler_populacao_init())
		{
			criar_individuo(gerar_matriz(), 0);
		}
		
	}
	
	/**
	 * Faz print da matriz do melhor individuo
	 *
	 */
	protected void printar()
	{
		int [][]tmp=best.ler_matriz();
		for(int i=0; i < Controlo.ler_n(); i++) {
			for(int j=0; j < Controlo.ler_m(); j++) {
				System.out.print(tmp[i][j]+ " ") ;
			}
			System.out.print("\n");
		}
	}
	
}
